/*
 * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 */
package jsyntaxpane.actions.gui;

import java.awt.Dialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.DefaultComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;

import jmine.tec.synthetic.All;
import jsyntaxpane.actions.ActionUtils;
import jsyntaxpane.actions.gui.renderer.SyntheticComboCellRenderer;
import jsyntaxpane.util.ReflectUtils;
import jsyntaxpane.util.SwingUtils;
import jsyntaxpane.util.intellisense.AutocompleteTool;
import jsyntaxpane.util.intellisense.bsh.BshAutocompleteTool;

/**
 * @author Ayman Al-Sairafi
 */
public class BeanshellReflectCompletionDialog extends javax.swing.JDialog {

    private static final int PREF_SMALL_SIZE = 156;

    private static final int PREF_BIG_SIZE = 437;

    private final AutocompleteTool tool = new BshAutocompleteTool();

    /**
     * The class we are displaying its members:
     */
    private volatile Class<?> theClass;

    /**
     * The current filter, to avoid refiltering the items
     */
    private String escapeChars = ";(= \t\n";

    private final JTextComponent target;

    private final Map<Class<?>, List<Member>> members = new LinkedHashMap<Class<?>, List<Member>>();

    /**
     * @param owner {@link Dialog}
     * @param target {@link JTextComponent}
     */
    public BeanshellReflectCompletionDialog(Dialog owner, JTextComponent target) {
        super(owner, true);
        this.target = target;
        this.initComponents();
        this.registerDocumentListener();
    }

    /**
     * @param owner {@link Frame}
     * @param target {@link JTextComponent}
     */
    public BeanshellReflectCompletionDialog(Frame owner, JTextComponent target) {
        super(owner, true);
        this.target = target;
        this.initComponents();
        this.registerDocumentListener();
    }

    /**
     * 
     */
    private void registerDocumentListener() {
        this.jTxtItem.getDocument().addDocumentListener(new DocumentListener() {

            public void insertUpdate(DocumentEvent e) {
                BeanshellReflectCompletionDialog.this.refilterList();
            }

            public void removeUpdate(DocumentEvent e) {
                BeanshellReflectCompletionDialog.this.refilterList();
            }

            public void changedUpdate(DocumentEvent e) {
                BeanshellReflectCompletionDialog.this.refilterList();
            }
        });
        // This will allow the textfield to receive TAB keys
        this.jTxtItem.setFocusTraversalKeysEnabled(false);
        // Add action so we automatically filter on comboBox Enter Key
        this.jCmbClassName.getEditor().addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) /* => */{
                BeanshellReflectCompletionDialog.this.refilterList();
                BeanshellReflectCompletionDialog.this.jTxtItem.requestFocusInWindow();
            }
        });
        SwingUtils.addEscapeListener(this);

        this.jLstItems.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 1) {
                    BeanshellReflectCompletionDialog.this.jTxtItem.requestFocusInWindow();
                } else if (e.getClickCount() > 1) {
                    BeanshellReflectCompletionDialog.this.processInsertion('\n');
                }
            }
        });

    }

    /**
     * @param font {@link Font}
     */
    public void setFonts(Font font) {
        this.jTxtItem.setFont(font);
        this.jLstItems.setFont(font);
        this.doLayout();
    }

    /**
     * Refiltra a lista
     */
    private void refilterList() {
        Class<?> cls = (Class<?>) this.jCmbClassName.getSelectedItem();
        this.theClass = cls;
        String prefix = this.jTxtItem.getText().toLowerCase();
        Vector<Member> filtered = new Vector<Member>();
        List<Member> list = this.members.get(cls);
        list = list == null ? new ArrayList<Member>() : list;
        for (Member m : list) {
            if (m.getName().toLowerCase().startsWith(prefix)) {
                filtered.add(m);
            }
        }
        this.jLstItems.setListData(filtered);
        this.selectNonEmptyItem();
        this.jLstItems.setSelectedIndex(0);
        this.jLstItems.requestFocusInWindow();
    }

    /**
     * 
     */
    private void selectNonEmptyItem() {

    }

    /**
     * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this
     * method is always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        this.jTxtItem = new javax.swing.JTextField();
        this.jScrollPane1 = new javax.swing.JScrollPane();
        this.jLstItems = new javax.swing.JList();
        this.jCmbClassName = new javax.swing.JComboBox();

        this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        this.setName("CompletionDialog"); // NOI18N
        this.setResizable(false);
        this.setUndecorated(true);

        this.jTxtItem.setBorder(null);
        this.jTxtItem.addKeyListener(new java.awt.event.KeyAdapter() {
            @Override
            public void keyPressed(java.awt.event.KeyEvent evt) {
                BeanshellReflectCompletionDialog.this.jTxtItemKeyPressed(evt);
            }
        });

        this.jLstItems.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        this.jLstItems.setCellRenderer(new BshMembersListRenderer(this));
        this.jLstItems.setFocusable(false);
        this.jScrollPane1.setViewportView(this.jLstItems);

        this.jCmbClassName.setModel(new javax.swing.DefaultComboBoxModel(new String[]{ "Object", "String" }));
        this.jCmbClassName.setRenderer(new SyntheticComboCellRenderer());
        this.jCmbClassName.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                BeanshellReflectCompletionDialog.this.jCmbClassNameItemStateChanged(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this.getContentPane());
        this.getContentPane().setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                .add(this.jTxtItem, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, PREF_BIG_SIZE, Short.MAX_VALUE)
                .add(this.jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, PREF_BIG_SIZE, Short.MAX_VALUE)
                .add(this.jCmbClassName, 0, PREF_BIG_SIZE, Short.MAX_VALUE));
        layout.setVerticalGroup(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING).add(
                layout.createSequentialGroup()
                        .add(this.jTxtItem, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
                                org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .add(0, 0, 0)
                        .add(this.jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, PREF_SMALL_SIZE, Short.MAX_VALUE)
                        .add(0, 0, 0)
                        .add(this.jCmbClassName, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
                                org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)));

        this.pack();
    }// </editor-fold>//GEN-END:initComponents

    /**
     * @param evt KeyEvent
     */
    private void jTxtItemKeyPressed(java.awt.event.KeyEvent evt) {// GEN-FIRST:event_jTxtItemKeyPressed

        int i = this.jLstItems.getSelectedIndex();
        switch (evt.getKeyCode()) {
        case KeyEvent.VK_ESCAPE:
            String result = this.jTxtItem.getText();
            ActionUtils.insertMagicString(this.target, result);
            this.setVisible(false);
            return;
        case KeyEvent.VK_DOWN:
            i++;
            evt.consume();
            break;
        case KeyEvent.VK_UP:
            i--;
            evt.consume();
            break;
        case KeyEvent.VK_HOME:
            i = 0;
            break;
        case KeyEvent.VK_END:
            i = this.jLstItems.getModel().getSize() - 1;
            break;
        case KeyEvent.VK_PAGE_DOWN:
            i += this.jLstItems.getVisibleRowCount();
            break;
        case KeyEvent.VK_PAGE_UP:
            i -= this.jLstItems.getVisibleRowCount();
            break;

        default:
            break;
        }

        if (this.escapeChars.indexOf(evt.getKeyChar()) >= 0) {
            char pressed = evt.getKeyChar();
            this.processInsertion(pressed);
        } else {
            // perform bounds checks for i
            if (i >= this.jLstItems.getModel().getSize()) {
                i = this.jLstItems.getModel().getSize() - 1;
            }
            if (i < 0) {
                i = 0;
            }
            this.jLstItems.setSelectedIndex(i);
            this.jLstItems.ensureIndexIsVisible(i);
            this.jLstItems.repaint();
        }
    }// GEN-LAST:event_jTxtItemKeyPressed

    /**
     * @param pressed char
     */
    private void processInsertion(char pressed) {
        String result;
        if (this.jLstItems.getSelectedIndex() >= 0) {
            Object selected = this.jLstItems.getSelectedValue();
            if (selected instanceof Method) {
                result = ReflectUtils.getJavaCallString((Method) selected);
            } else if (selected instanceof Constructor<?>) {
                result = ReflectUtils.getJavaCallString((Constructor<?>) selected);
            } else if (selected instanceof Field) {
                result = ((Field) selected).getName();
            } else {
                result = selected.toString();
            }
        } else {
            result = this.jTxtItem.getText();
        }

        // we need to just accept ENTER, and replace the tab with a single
        // space
        if (pressed != '\n') {
            result += (pressed == '\t') ? ' ' : pressed;
        }
        ActionUtils.insertMagicString(this.target, result);
        this.setVisible(false);
    }

    /**
     * @param evt ItemEvent
     */
    private void jCmbClassNameItemStateChanged(java.awt.event.ItemEvent evt) {// GEN-FIRST:event_jCmbClassNameItemStateChanged
        if (evt.getStateChange() == ItemEvent.SELECTED) {
            this.refilterList();
        }
    }// GEN-LAST:event_jCmbClassNameItemStateChanged

    /**
     * Atualiza a lista de membros a ser exibida
     */
    private void updateItems() {
        this.members.clear();
        final SortedSet<Member> membersToInsert = new TreeSet<Member>(BshAutocompleteTool.MEMBER_NAME_COMPARATOR);
        membersToInsert.addAll(this.tool.getCompletionFor(this.target.getText(), this.target.getCaretPosition()));
        this.members.put(All.class, new ArrayList<Member>(membersToInsert));
        for (Member member : membersToInsert) {
            List<Member> list = this.members.get(member.getDeclaringClass());
            if (list == null) {
                list = new ArrayList<Member>();
                this.members.put(member.getDeclaringClass(), list);
            }
            list.add(member);
        }
        final Object[] array = this.members.keySet().toArray();
        this.jCmbClassName.setModel(new DefaultComboBoxModel(array));
        this.jCmbClassName.setSelectedIndex(0);
        this.refilterList();
    }

    /**
     * @return {@link Class}
     */
    public Class<?> getTheClass() {
        return this.theClass;
    }

    /**
     * Display the dialog.
     * 
     * @param targetComponent text component (its Window will be the parent)
     */
    public void displayFor(JTextComponent targetComponent) {
        final int caret = targetComponent.getCaretPosition();
        String current = this.tool.getCurrentToken(targetComponent.getText(), caret);
        this.updateItems();
        try {
            int dot = targetComponent.getCaretPosition() - current.length();
            targetComponent.select(dot, targetComponent.getCaretPosition());
            targetComponent.replaceSelection("");
            Window window = SwingUtilities.getWindowAncestor(targetComponent);
            Rectangle rt = targetComponent.modelToView(dot);
            Point loc = new Point(rt.x, rt.y);
            // convert the location from Text Componet coordinates to
            // Frame coordinates...
            loc = SwingUtilities.convertPoint(targetComponent, loc, window);
            // and then to Screen coordinates
            SwingUtilities.convertPointToScreen(loc, window);
            this.setLocationRelativeTo(window);
            this.setLocation(loc);
        } catch (BadLocationException ex) {
            Logger.getLogger(BeanshellReflectCompletionDialog.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.setFonts(targetComponent.getFont());
        this.jTxtItem.setText(current);
        this.refilterList();
        this.setVisible(true);
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JComboBox jCmbClassName;

    private javax.swing.JList jLstItems;

    private javax.swing.JScrollPane jScrollPane1;

    private javax.swing.JTextField jTxtItem;

    // End of variables declaration//GEN-END:variables

    /**
     * @return the tool
     */
    public AutocompleteTool getTool() {
        return this.tool;
    }

    /**
     * @param text {@link String}
     * @param host {@link Class}
     */
    public void loadInitializer(String text, Class<?> host) {
        this.tool.loadInitialization(text, host);
    }

    /**
     * @param name String
     * @param type Class
     * @param host Class
     */
    public void addGlobalVariable(String name, Class<?> type, Class<?> host) {
        this.tool.addGlobalVariable(type, name, host);
    }

}
